package MetaInit

import (
	"fmt"
	"db2s/Er"
	"db2s/arg"
	"db2s/global"
	"reflect"
	"strings"
)

// 库名表名做合并处理，schema -->table1,table2
func (t TableMeta) schemaToString(schemaTable []string) (schemaS map[string][]string) {
	var (
		schemaName = make(map[string][]string)
	)
	//库名切割
	for _, v := range schemaTable {
		if strings.Contains(v, global.SchemaTableSplit) {
			f1 := strings.Split(v, global.SchemaTableSplit)
			if v1, ok := schemaName[f1[0]]; ok {
				schemaName[f1[0]] = append(v1, f1[1])
			} else {
				schemaName[f1[0]] = []string{f1[1]}
			}
		}
	}
	return schemaName
}
func getColumnDataEr(dbType string) (s Er.TableColumnEr, err error) {
	if s, err = Er.ColumnData(Er.TableColumn{DBType: dbType}); err != nil {
		return
	}
	return
}
func (t TableMeta) getSrcColumnTypeInputParameter(v arg.InputTableAttributes) global.GetColumnTypeInput {
	return global.GetColumnTypeInput{
		LogSeq: t.LogSeq,
		DB:     t.SDB,
		TableInfo: global.TableInfo{
			Schema: v.Schema,
			Table:  v.Table,
		},
		Input: t.Input,
	}
}
func (t TableMeta) getDstColumnTypeInputParameter(v arg.InputTableAttributes) global.GetColumnTypeInput {
	return global.GetColumnTypeInput{
		LogSeq: t.LogSeq,
		DB:     t.DDB,
		TableInfo: global.TableInfo{
			Schema: v.MSchema,
			Table:  v.MTable,
		},
		Input: t.Input,
	}
}
func (t TableMeta) getSrcTableMetaInfoInputParameter(v arg.InputTableAttributes) global.TablesMetaInfoInput {
	return global.TablesMetaInfoInput{
		LogSeq: t.LogSeq,
		DB:     t.SDB,
		TableInfo: global.TableInfo{
			Schema:      v.Schema,
			Table:       v.Table,
			HiddenIndex: t.HiddenIndex,
		},
		Input: t.Input,
	}
}
func (t TableMeta) getAll(s Er.TableColumnEr, v arg.InputTableAttributes, all global.GetColumnTypeInput) (schema, table string, Result []global.TableMeta, err error) {
	var (
		result global.AllReturnResult
		event  = "[getAll]"
	)
	if result, err = s.All(all); err != nil {
		vlog := fmt.Sprintf("(%v) %v Failed to obtain column of source table %v.%v!!! error is {%v}", t.LogSeq, v.Schema, v.Table, event, err)
		errorWriteInfo(vlog)
		return
	}
	schema = v.Schema
	table = v.Table
	Result = result.Result
	infoWriteInfo(fmt.Sprintf("(%v) %v Column of source table %v.%v is obtained successfully!!!", t.LogSeq, event, v.Schema, v.Table))
	debugWriteInfo(fmt.Sprintf("(%v) %v Column of source table %v.%v is obtained successfully!!! data is {%v}", t.LogSeq, event, v.Schema, v.Table, global.AnyToJsonString(result)))
	return
}

type ObjectMetaInput struct {
	TableAttributes arg.InputTableAttributes
	TIndex          IndexChooseResult
}

// StructInitToColumn 查询表的所有列信息
func (t TableMeta) StructInitToColumn(v ObjectMetaInput) (f global.TableMetaGather, err error) {
	var (
		event = "[StructInitToColumn]"
		s, d  Er.TableColumnEr
	)
	if s, err = getColumnDataEr(t.DSns.SrcDBName); err != nil {
		return
	}
	infoWriteInfo(fmt.Sprintf("(%d) %v Start getting column information of table %v.%v.", t.LogSeq, event, v.TableAttributes.Schema, v.TableAttributes.Table))
	if f.Schema, f.Table, f.SourceM, err = t.getAll(s, v.TableAttributes, t.getSrcColumnTypeInputParameter(v.TableAttributes)); err != nil || !strings.EqualFold(v.TableAttributes.TableJoin, "join") {
		return
	}
	if d, err = getColumnDataEr(t.DSns.DestDBName); err != nil {
		return
	}
	if f.MSchema, f.MTable, f.DestM, err = t.getAll(d, v.TableAttributes, t.getDstColumnTypeInputParameter(v.TableAttributes)); err != nil {
		return
	}
	return
}
func (t TableMeta) getIndexColumn(v arg.InputTableAttributes, p Er.TablesMetaInfoEr) (f []global.IndexColumn, err error) {
	var (
		queryData any
	)
	if queryData, err = p.IndexColumn(t.getSrcTableMetaInfoInputParameter(v)); err != nil {
		return
	}
	if queryData != nil {
		f = queryData.([]global.IndexColumn)
	}
	return
}
func (t TableMeta) getSchemaTableMerge(s, l string) string {
	return fmt.Sprintf("%v%v%v", s, global.SchemaTableSplit, l)
}
func (t TableMeta) getSchemaTableSeparation(s string) []string {
	return strings.Split(s, global.SchemaTableSplit)
}

func (t TableMeta) ForceIndex(i arg.InputTableAttributes, s []global.IndexColumn) (s1 IndexChooseResult) {
	var (
		event            = "[forceIndex]"
		l                Er.TablesMetaInfoEr
		err              error
		forceIndexName   string
		forceIndexColumn []string
	)
	if l, err = Er.MetaInfo(Er.TableInfoMeta{
		DBType: t.DSns.SrcDBName,
	}); err != nil {
		return IndexChooseResult{}
	}
	WriteLog.Info(fmt.Sprintf("(%v) %v Start processing the force index option of the table %v.%v. force Index Name is {%v}", t.LogSeq, event, i.Schema, i.Table, i.TableAttributes.ForceIndex))
	force := strings.ReplaceAll(i.TableAttributes.ForceIndex, "\"", "")
	if strings.Contains(force, "(") && strings.HasSuffix(force, ")") {
		var tmpStr string
		if n := strings.Index(force, "("); n != -1 {
			forceIndexName = force[:n]
			tmpStr = force[n+1 : len(force)-1]
		}
		forceIndexColumn = strings.Split(tmpStr, ",")
	} else {
		forceIndexName = force
	}
	if strings.EqualFold(forceIndexName, "missIndex") {
		WriteLog.Info(fmt.Sprintf("(%v) %v Table %v.%v is forced to go into no-index mode.", t.LogSeq, event, i.Schema, i.Table))
		return IndexChooseResult{
			IndexType:     "forceMiss",
			IndexCategory: "0",
		}
	}
	q := l.IndexMerge(global.TablesMetaInfoInput{
		LogSeq:      t.LogSeq,
		IndexColumn: s,
	})
	WriteLog.Debug(fmt.Sprintf("(%v) %v Table %v.%v index merge successful. merge data is {%v}", t.LogSeq, event, i.Schema, i.Table, global.AnyToJsonString(q)))
	var columnInfo []global.IndexColumn
	for k, v := range q.IndexColumn {
		if strings.EqualFold(k, forceIndexName) {
			if len(forceIndexColumn) == 0 {
				columnInfo = v
			} else {
				for _, vv := range v {
					for _, ww := range forceIndexColumn {
						if strings.EqualFold(vv.ColumnName, ww) {
							columnInfo = append(columnInfo, vv)
						}
					}
				}
			}
			break
		}
	}
	n := IndexChooseResult{
		IndexType: q.IndexType[forceIndexName],
		IndexName: forceIndexName,
		IndexColumn: func() (z []string) {
			for _, y := range columnInfo {
				z = append(z, y.ColumnName)
			}
			return
		}(),
		IndexCategory: func() string {
			if len(columnInfo) > 0 {
				return columnInfo[len(columnInfo)-1].IndexCategory
			}
			return ""
		}(),
		ColumnSeq: func() (g map[string]string) {
			g = make(map[string]string)
			for _, x := range columnInfo {
				g[x.IndexSeq] = x.ColumnName
			}
			return
		}(),
		Cardinality: func() string {
			if len(columnInfo) > 0 {
				return columnInfo[len(columnInfo)-1].Cardinality
			}
			return "0"
		}(),
	}
	WriteLog.Info(fmt.Sprintf("(%v) %v Index selection of table %v is completed. result is {%v}", t.LogSeq, event, strings.Join(t.getSchemaTableSeparation(forceIndexName), "."), global.AnyToJsonString(n)))
	return n
}

func (t TableMeta) IndexInitToColumn(v ObjectMetaInput) (p IndexChooseResult, err error) {
	var (
		event            = "[IndexInitToColumn]"
		indexColumnGarth []global.IndexColumn
		l                Er.TablesMetaInfoEr
	)
	WriteLog.Debug(fmt.Sprintf("(%v) %v Start initializing the index data of table %v.%v", t.LogSeq, event, v.TableAttributes.Schema, v.TableAttributes.Table))
	//获取索引列数据
	if l, err = getMetaInfoEr(t.DSns.SrcDBName); err != nil {
		return
	}
	if indexColumnGarth, err = t.getIndexColumn(v.TableAttributes, l); err != nil {
		WriteLog.Error(fmt.Sprintf("(%v) %v Initialization of index metadata of source table %v.%v object failed!!! error is {%v}", t.LogSeq, event, v.TableAttributes.Schema, v.TableAttributes.Table, err))
		return
	}
	if len(indexColumnGarth) == 0 {
		WriteLog.Info(fmt.Sprintf("(%v) %v index metadata is empty of source table %v.%v", t.LogSeq, event, v.TableAttributes.Schema, v.TableAttributes.Table))
		return
	}
	WriteLog.Info(fmt.Sprintf("(%v) %v Initialization of index metadata of source table %v.%v object successful!!!", t.LogSeq, event, v.TableAttributes.Schema, v.TableAttributes.Table))
	WriteLog.Debug(fmt.Sprintf("(%v) %v Initialization of index metadata of source table %v.%v object successful!!! data is {%v}", t.LogSeq, event, v.TableAttributes.Schema, v.TableAttributes.Table, global.AnyToJsonString(indexColumnGarth)))
	if len(v.TableAttributes.TableAttributes.ForceIndex) > 0 {
		return t.ForceIndex(v.TableAttributes, indexColumnGarth), nil
	}
	//处理强制索引
	result, err := l.IndexChoice(global.TablesMetaInfoInput{
		LogSeq:      t.LogSeq,
		IndexColumn: indexColumnGarth,
	})
	if !strings.EqualFold(fmt.Sprintf("%v", reflect.TypeOf(result)), "global.IndexSelection") {
		return IndexChooseResult{}, err
	}
	i := result.(global.IndexSelection)
	if len(i.IndexColumn) == 0 {
		WriteLog.Info(fmt.Sprintf("(%v) %v The index data of table %v.%v is empty and the indexless mode is used.", t.LogSeq, event, v.TableAttributes.Schema, v.TableAttributes.Table))
		return IndexChooseResult{
			IndexType:   "missIndex",
			Cardinality: "0",
		}, nil
	}
	//非强制索引，使用索引自动选择算法
	q := IndexChooseResult{
		IndexType: i.IndexType,
		IndexName: i.IndexName,
		IndexColumn: func() (z []string) {
			for _, y := range i.IndexColumn {
				z = append(z, y.ColumnName)
			}
			return
		}(),
		ColumnSeq: func() (g map[string]string) {
			g = make(map[string]string)
			for _, x := range i.IndexColumn {
				g[x.IndexSeq] = x.ColumnName
			}
			return
		}(),
		IndexCategory: func() string {
			return i.IndexColumn[len(i.IndexColumn)-1].IndexCategory
		}(),
		Cardinality: func() string {
			return i.IndexColumn[len(i.IndexColumn)-1].Cardinality
		}(),
	}
	WriteLog.Info(fmt.Sprintf("(%v) %v Start to perform index selection on table %v.%v according to the algorithm. index Data is {%v}", t.LogSeq, event, v.TableAttributes.Schema, v.TableAttributes.Table, global.AnyToJsonString(q)))
	return q, nil
}

func pcgInit(i pcgInitS) global.TablesMetaInfoInput {
	return global.TablesMetaInfoInput{
		LogSeq: i.logSeq,
		DB:     i.db,
		TableInfo: global.TableInfo{
			Schema: i.schema,
			Table:  i.table,
		},
		Input: i.input,
	}
}

// IndexCategoryMergePartition 根据索引类别的不同选择不同的分区方式，如果选中的索引是全局索引，则不能走分区
func (t TableMeta) IndexCategoryMergePartition(v *arg.InputTableAttributes, tIndex IndexChooseResult, TablePartition global.PartitionGarth) (p global.PartitionGarth) {
	var (
		event = "[IndexCategoryMergePartition]"
		//table1 = strings.Join(t.getSchemaTableSeparation(tableName), ".")
	)
	if len(TablePartition.Name.Source) == 0 || len(TablePartition.Name.Dest) == 0 {
		p.Name = global.PartitionGarthName{
			Source: []string{"single"},
			Dest:   []string{"single"},
		}
		p.Shard = global.PartitionGarthShard{
			Source: map[string]string{"single": "single"},
			Dest:   map[string]string{"single": "single"},
		}
		p.Logo = global.PartitionGarthLogo{
			Source: "single",
			Dest:   "single",
		}
		p.SourceDestNameMap = map[string]string{"single": "single"}
		p.BackendTableName = global.PartitionGarthBackendTableName{
			Source: map[string]global.BackendTable{"single": {
				ShardName:        "single",
				BackendTableName: v.Table,
			}},
			Dest: map[string]global.BackendTable{"single": {
				ShardName:        "single",
				BackendTableName: v.MTable,
			}},
		}
		p.Object = TablePartition.Object
		WriteLog.Info(fmt.Sprintf("(%v) %v Table %v.%v is single at the source or target. partition Name is {source:%v dest:%v} result is {%v}", t.LogSeq, event, v.Schema, v.Table, TablePartition.Name.Source, TablePartition.Name.Dest, global.AnyToJsonString(p)))
		return
	}
	if strings.EqualFold(tIndex.IndexType, "forceMiss") || len(tIndex.IndexColumn) == 0 {
		WriteLog.Info(fmt.Sprintf("(%v) %v Table %v.%v is missIndex at the source. source index info {type:%v indexColumn:%v} result is {%v}", t.LogSeq, event, v.Schema, v.Table, tIndex.IndexType, tIndex.IndexColumn, global.AnyToJsonString(TablePartition)))
		return TablePartition
	}
	switch {
	case strings.EqualFold(tIndex.IndexCategory, "local"):
		WriteLog.Info(fmt.Sprintf("(%v) %v Table %v.%v index Category is {local} at the source. result is {%v}", t.LogSeq, event, v.Schema, v.Table, global.AnyToJsonString(TablePartition)))
		return TablePartition
	case strings.EqualFold(tIndex.IndexCategory, "global"):
		if strings.EqualFold(TablePartition.Object, "single") { //
			return TablePartition
		}
		p.Name = global.PartitionGarthName{
			Source: []string{"single"},
			Dest:   []string{"single"},
		}
		p.Shard = global.PartitionGarthShard{
			Source: map[string]string{"single": "single"},
			Dest:   map[string]string{"single": "single"},
		}
		p.Logo = global.PartitionGarthLogo{
			Source: "single",
			Dest:   "single",
		}
		p.SourceDestNameMap = map[string]string{"single": "single"}
		p.BackendTableName = global.PartitionGarthBackendTableName{
			Source: map[string]global.BackendTable{"single": {
				ShardName:        "single",
				BackendTableName: v.Table,
			}},
			Dest: map[string]global.BackendTable{"single": {
				ShardName:        "single",
				BackendTableName: v.MTable,
			}},
		}
		p.Object = TablePartition.Object
		WriteLog.Info(fmt.Sprintf("(%v) %v Table %v.%v index Category is {global} at the source. result is {%v}", t.LogSeq, event, v.Schema, v.Table, global.AnyToJsonString(p)))
	}
	return p
}

// TableObjectSeqOptimizer 根据特定规则优化表的顺序，按照从快到慢的原则
func (t TableMeta) TableObjectSeqOptimizer(tableList []string, shard map[string]string) (b []string) {
	var (
		tableAvgRowLength = make(map[string]any)
		newTable          []string
		s                 Er.TablesMetaInfoEr
		l                 Er.TableActiveEr
		err               error
	)
	//获取平均行长度
	if s, err = Er.MetaInfo(Er.TableInfoMeta{DBType: t.DSns.SrcDBName}); err != nil {
		return nil
	}
	if l, err = Er.TActive(Er.TableActive{
		DBType: func() string {
			if len(t.DSns.DestDBName) > 0 {
				return t.DSns.DestDBName
			} else {
				return t.DSns.SrcDBName
			}
		}(),
	}); err != nil {
		return nil
	}
	for _, v := range tableList {
		v1 := t.getSchemaTableSeparation(v)
		if a, err := s.AvgRowLength(
			global.TablesMetaInfoInput{
				LogSeq: 1,
				DB:     t.SDB,
				TableInfo: global.TableInfo{
					Schema: v1[0],
					Table:  v1[1],
				},
			}); err != nil {
		} else {
			tableAvgRowLength[v] = a
		}
	}
	if newt, err := l.OptimizerOrderBy(global.TableActiveInput{
		OptimizerOrderBy: global.OptimizerOrderBy{
			SrcArgRowsLength: tableAvgRowLength,
			DestShardName:    shard,
		},
	}); err != nil {
		return nil
	} else {
		newTable = newt.([]string)
	}
	return newTable
}
